#include <stdlib.h>

#include "Vsystem.h"
#include "verilated_vcd_c.h"

static void print_final_state(const Vsystem *system)
{
#define R(x) printf(#x "=%08x\n", system->system__DOT__cpu__DOT__rf__DOT__##x)
    R(r00_usr);
    R(r01_usr);
    R(r02_usr);
    R(r03_usr);
    R(r04_usr);
    R(r05_usr);
    R(r06_usr);
    R(r07_usr);
    R(r08_usr);
    R(r09_usr);
    R(r10_usr);
    R(r11_usr);
    R(r12_usr);
    R(r13_usr);
    R(r14_usr);
    R(r13_svc);
    R(r14_svc);
    R(r13_irq);
    R(r14_irq);
    R(r13_und);
    R(r14_und);
    printf("pc=%08x\n", system->system__DOT__cpu__DOT____Vcellout__pc__q << 2);
#undef R
}

static void usage(const char *arg0)
{
    fprintf(stderr, "usage: %s [-t VCD_FILE] IMAGE\n", arg0);
    exit(1);
}

int main(int argc, char **argv)
{
    const char *trace_file_path = nullptr;
    int dump_start = 0;
    int dump_length = 0;
    const char *dump_file = nullptr;
    uint64_t time_limit = 1000;
    const char *uart_out_file_path = "uart_out.bin";

    int opt;
    int longopt_index;
    while ((opt = getopt(argc, argv, "d:l:m:s:t:u:")) >= 0) {
        switch (opt) {
        case 's': // dump-start
            dump_start = atoi(optarg);
            break;
        case 'l': // dump-length
            dump_length = atoi(optarg);
            break;
        case 'm': // time limit
            time_limit = atoi(optarg);
            break;
        case 'd': // dump-file
            dump_file = optarg;
            break;
        case 't':
            trace_file_path = optarg;
            break;
        case 'u':
            uart_out_file_path = optarg;
            break;
        default: // '?'
            usage(argv[0]);
        }
    }

    if (optind >= argc) {
        usage(argv[0]);
    }

    const char *image_path = argv[optind];

    vluint64_t time = 0;

    FILE *fp_image = fopen(image_path, "rb");
    if (!fp_image) {
        perror(image_path);
        return 1;
    }

    FILE *fp_uart = fopen(uart_out_file_path, "wb");
    if (!fp_uart) {
        perror(uart_out_file_path);
        return 1;
    }

    Verilated::commandArgs(argc, argv);
    Verilated::traceEverOn(true);

    Vsystem *system = new Vsystem;

    void *data = (void*)(system->system__DOT__ram__DOT__blk_mem__DOT__m);

    size_t nread;
    nread = fread(data, 1, 0x10000, fp_image);
    if (nread < 0x10000) {
        fprintf(stderr, "short read (%lu)\n", nread);
    }

    VerilatedVcdC *trace = nullptr;
    if (trace_file_path) {
        trace = new VerilatedVcdC;
        system->trace(trace, 99);
        trace->open(trace_file_path);
    }

    uint8_t uart_state = 0;
    uint8_t uart_tx;

    system->clk = 0;
    while (!Verilated::gotFinish() && time < time_limit) {
        system->clk = ~system->clk & 1;
        system->rst = time<20;
        system->eval();
        if (system->clk) {
            switch (uart_state) {
            case 0:
                if (!system->uart_tx) {
                    uart_state = 1;
                    uart_tx = 0;
                }
                break;
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
                uart_state++;
                uart_tx = uart_tx >> 1 | (system->uart_tx&1) << 7;
                break;
            case 9:
                uart_state = 0;
                fputc(uart_tx, fp_uart);
                break;
            }
        }
        //printf("%llu %u\n", time, system->counter);
        if (trace_file_path) {
            trace->dump(time);
        }
        time += 5;
    }

    print_final_state(system);

    if (trace_file_path) {
        trace->close();
        delete trace;
    }

    if (dump_file) {
        FILE *dump_fp = fopen(dump_file, "wb");
        fwrite((uint8_t*)(system->system__DOT__ram__DOT__blk_mem__DOT__m) + dump_start, 1, dump_length, dump_fp);
        fclose(dump_fp);
    }

    delete system;

    return 0;
}
